Skip to content

Modernize and bring up to date with JS pixelmatch#8

Open
mourner wants to merge 7 commits intomasterfrom
mourner/modernize
Open

Modernize and bring up to date with JS pixelmatch#8
mourner wants to merge 7 commits intomasterfrom
mourner/modernize

Conversation

@mourner
Copy link
Copy Markdown
Member

@mourner mourner commented Apr 28, 2026

Build system

  • Replaced gyp + bundled Python with a small CMakeLists.txt (header-only INTERFACE library, mapbox::pixelmatch alias, install rules for downstream find_package).
  • Removed deps/gyp/, deps/run_gyp, pixelmatch.gyp, the wrapper Makefile, and .travis.yml.
  • New GitHub Actions workflow (Ubuntu-only — header-only stdlib code, no platform variance to test).
  • Added .clang-format enforcement in CI via clang-format --dry-run --Werror.

Tests

  • Tests now decode PNG fixtures directly via a vendored picoPNG (~530-line single header), eliminating the JS/pngjs convert_tests.js step and the committed *.rgba blobs.
  • Imported new fixtures (1, 3, 4, 6 from upstream; 2, 5, 7 generated against an upstream branch with checkerboard: false to match our white-blend semantics).

Algorithm port (from JS pixelmatch)

Brought the implementation up to date with ~10 years of upstream improvements, while deliberately keeping:

  • White-background blending (skipping the new checkerboard variant).
  • The original options surface (no alpha / aaColor / diffColor / diffColorAlt / diffMask).

Performance changes

  1. Identical-image fast path — per-row memcmp short-circuits the entire loop when both inputs are byte-equal.
  2. Per-pixel 4-byte equality check before invoking colorDelta, so identical pixels skip all YIQ math.
  3. colorDelta rewrite — operates on raw channel diffs and only blends with white when α<255. The opaque case (the common one) now skips the blend math entirely. Mathematically equivalent to the old white-blend formula.
  4. antialiased rewrite:
    • Center pixel cached outside the 8-neighbor loop (one read instead of nine).
    • Edge-pixel zeroes initialization fix (treats off-image neighbors as implicit zeroes, matching upstream behavior).
    • The recursive call to antialiased(...) for the sibling-confirmation step replaced with a much simpler hasManySiblings doing raw RGBA equality via memcmp. This was the biggest single win.
  5. Stride-aware antialiased and hasManySiblings (closes a latent bug where the AA path assumed tightly-packed input even when called via the stride overload).

Correctness changes

  • drawGrayPixel now reads from img1 with the stride-aware offset (was using the tightly-packed output offset, broken for stride ≠ width × 4).
  • New AA edge-pixel zeroes=1 initialization fixes a small over-flagging on image borders. This is what changes the 4a vs 4b count from 36089 → 36049 (40 pixels at edges previously misclassified as differences).
  • All four reused upstream diff fixtures (1, 3, 4, 6) now match byte-for-byte.

Benchmark results

Added a bench_pixelmatch executable that runs the full fixture set with auto-calibrated iteration counts.

Case Before After Speedup
1a vs 1b (512×256) 1.16 ms 0.36 ms 3.2×
2a vs 2b (256×256) 3.89 ms 1.73 ms 2.3×
3a vs 3b (512×256) 1.20 ms 0.31 ms 3.9×
4a vs 4b (438×412) 9.13 ms 3.98 ms 2.3×
5a vs 5b (256×256) 0.61 ms 0.15 ms 4.1×
6a vs 6b (256×256) 1.29 ms 0.45 ms 2.9×
7a vs 7b (500×500) 2.84 ms 0.86 ms 3.3×
Total 20.1 ms 7.8 ms 2.6×

@mourner mourner changed the title Modernize the project and bringing up to speed with the original Modernize and bring up to date with JS pixelmatch Apr 28, 2026
@mourner mourner requested a review from kkaefer April 28, 2026 20:36
@mourner mourner added the ai AI coding agents co-authored the code label Apr 28, 2026
@mourner mourner marked this pull request as ready for review April 28, 2026 20:36
Comment thread test/bench.cpp
{"7a", "7b", 0.1},
};

using clock = std::chrono::steady_clock;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use Google Benchmark for more precise timing

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I think it's fine to have less precise numbers — +-5% noise here isn't critical since it's a testing library, but simpler approach can reliably detect substantial gains like 3x, and we keep the repo simple, no submodules / vendored files.

Comment thread test/picopng.hpp Outdated
@mourner mourner mentioned this pull request Apr 30, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai AI coding agents co-authored the code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants